import { useRef,useEffect,useState,useMemo, useEvent } from "react";
import "./Canvas.css";
import defultImg from '../assets/default.jpg'

import { getSinCos,getDeg,getCenter,rdmNum,rdmColor,deb,getAudioTime } from "../tools/canvasTools";


// ctx画图基类
class DrawClass {
  canvas;
  ctx;
  constructor() {
    if (!DrawClass.ctx && !DrawClass.canvas){
      throw new Error('DrawClass 必须添加canvas和ctx属性')
    }
    this.ctx = DrawClass.ctx;
    this.canvas = DrawClass.canvas;
  }
}

class DrawStar extends DrawClass {
  startPoint; // 画笔起点位置 (moveTo)
  points = []; // 落点位置集合 (lineTo)
  rotate; // 旋转角度
  rotateSpeed; //旋转速度
  x // 图像坐标
  y // 图像坐标
  size // 图像大小
  color // 图像颜色
  constructor(x, y, size) {
    super();
    this.x = x;
    this.y = y;
    this.size = size;
    this.init()
  }
  init(){
    this.rotate = 10;
    this.startPoint = { x: 0, y: 0 };
    this.color = rdmColor();
    let abs = Math.random() > 0.5 ? 1:-1
    this.rotateSpeed = abs * 0.5;
  }
  draw(){
    const { ctx, x, y, size, color } = this;
    ctx.save();
    ctx.translate(x, y);
    ctx.scale(size,size)
    ctx.rotate((this.rotate * Math.PI) / 180);
    ctx.beginPath();
    // 起点位置
    for (var i = 0;i < 5;i++) {
      ctx.lineTo(Math.cos( (18+i*72-30)/180*Math.PI )*100, -Math.sin( (18+i*72-30)/180*Math.PI )*100);
      ctx.lineTo(Math.cos( (54+i*72-30)/180*Math.PI )*100/2, -Math.sin( (54+i*72-30)/180*Math.PI )*100/2);
   }  //30旋转弧度
   ctx.closePath();
   ctx.strokeStyle = this.color
   ctx.lineWidth = 10
  //  ctx.fillStyle = this.color;
  //  ctx.fill();
   ctx.stroke();
   ctx.restore()
  }
}

class FlyStar extends DrawStar{
  limit = 5
  nodeArr = []; // 单例模式记录所有创建的子类
  static instance
  constructor(x, y, size){
    super(x, y, size)
    if (!FlyStar.instance) {
      FlyStar.instance = this
    }
    // 收集子类
    if (x !== undefined && FlyStar.instance.nodeArr.length <= this.limit) {
      FlyStar.instance.nodeArr.push(this);
    }
    return FlyStar.instance;
  }
  fly(){
    const x = this.canvas.width / 2
    const y = this.canvas.height / 2
    this.nodeArr.forEach((item,index) => {
      let speedx = (x - item.x)* 0.005
      let speedy = (y - item.y)* 0.005
      item.x += speedx;
      item.y += speedy;
      item.rotate += item.rotateSpeed
      let speed = 0
      if (Math.abs(speedx*0.01) > 0.03) {
        speed = Math.abs(speedx*0.01)
      } else {
        speed = item.size * 0.007
      }
      item.size -= speed
      if (item.size <= 0.01){
        this.nodeArr.splice(index,1)
        item = null
        return
      }
      item.draw();
    });
  }
}

// 画心 类
class DrawHeart extends DrawClass {
  startPoint; // 画笔起点位置 (moveTo)
  points = []; // 落点位置集合 (lineTo)
  rotate; // 旋转角度
  x // 图像坐标
  y // 图像坐标
  size // 图像大小
  color // 图像颜色
  constructor(x, y, size) {
    super();
    this.x = x;
    this.y = y;
    this.size = size;
    this.init()
  }
  init(){
    this.rotate = 10;
    this.startPoint = { x: 0, y: 0 };
    this.color = rdmColor();
  }
  draw() {
    const { ctx, x, y, size, color } = this;
    ctx.save();
    ctx.strokeStyle = "transparent";
    ctx.fillStyle = color;
    ctx.translate(x, y);
    ctx.rotate((this.rotate * Math.PI) / 180);
    // 绘制线条
    ctx.beginPath();
    // 起点位置
    ctx.moveTo(this.startPoint.x, this.startPoint.y);
    this.lineTo(this.startPoint, size, 45);
    // 获取圆弧起始点和终点
    let begin = this.lineTo(this.current, size, 315);
    let traget = this.lineTo(this.current, size, 225);
    let end = this.startPoint;

    this.points.forEach((point) => {
      ctx.lineTo(point.x, point.y);
    });
    this.points = [];
    // 绘制圆弧1
    this.arcTo( begin, getCenter(begin, traget), size / 2, getDeg(45), getDeg(225), 1);
    // 绘制圆弧2
    this.arcTo( traget, getCenter(traget, end), size / 2, getDeg(315), getDeg(135), 1);
    ctx.closePath();
    // ctx.stroke()
    ctx.fill();
    ctx.restore();
  }
  /**
   *
   * @param {*} orgin 起始位置坐标
   * @param {*} size 大小
   * @param {*} deg 角度
   */
  lineTo(orgin, size, deg) {
    let res = {
      x: orgin.x + size * getSinCos("cos", deg),
      y: orgin.y + size * getSinCos("sin", deg),
    };
    this.points.push(res);
    this.current = res;
    return res;
  }
  /**
   *
   * @param {*} begin 起始点位置 moveTo
   * @param {*} centerPoint 圆心位置
   * @param {*} r 半径
   * @param {*} startAngle 起始角度
   * @param {*} endAngle 结束角度
   * @param {*} anticlockwise 顺逆时针 1 / 0
   */
  arcTo(begin, centerPoint, r, startAngle, endAngle, anticlockwise) {
    const { ctx } = this;
    ctx.moveTo(begin.x, begin.y);
    ctx.arc( centerPoint.x, centerPoint.y, r, startAngle, endAngle, anticlockwise);
  }
}

// 工厂单例模式  定义类创建的个数 销毁方式 运动速度
function Factory(newClass,props){
  const F = function(x, y, size){
    this.limit = props.limit
    this.nodeArr = []
    if (!F.instance) {
      F.instance = this
    }
    // 收集子类
    if (x !== undefined && F.instance.nodeArr.length <= this.limit) {
      F.instance.nodeArr.push(this);
    }
    F.prototype = new newClass()
    this.x = x
    this.y = y
    this.size = size
    this.speedx = props.speedx ? props.speedx():(Math.random() - 0.5) * 0.01;
    this.speedy = props.speedy ? props.speedy():(Math.random() - 0.5) * 0.01;
    this.sizespeed = props.sizespeed ? props.sizespeed:0.05;
    return F.instance
  }
  F.prototype = new newClass()
  F.prototype.move = move
  return F
}
// 
function move(){
  const {canvas} = this
  this.nodeArr.forEach((item,index) => {
    item.x += item.speedx * canvas.width/4;
    item.y += item.speedy * item.y;
    item.rotate += item.speedx *500;
    let speed = item.size * item.sizespeed
    item.size -= speed
    if (item.size <= 2){
      this.nodeArr.splice(index,1)
      item = null
      return
    }
    item.draw();
  });
}


//  随机生成向中心飘落的心
class FlyHeart extends DrawHeart{
  limit = 5
  nodeArr = []; // 单例模式记录所有创建的子类
  static instance
  constructor(x, y, size){
    super(x, y, size)
    if (!FlyHeart.instance) {
      FlyHeart.instance = this
    }
    // 收集子类
    if (x !== undefined && FlyHeart.instance.nodeArr.length <= this.limit) {
      FlyHeart.instance.nodeArr.push(this);
    }
    return FlyHeart.instance;
  }
  fly(){
    const x = this.canvas.width / 2
    const y = this.canvas.height / 2
    this.nodeArr.forEach((item,index) => {
      let speedx = (x - item.x)* 0.005
      let speedy = (y - item.y)* 0.005
      item.x += speedx;
      item.y += speedy;
      item.rotate += 1;
      let speed = 0
      if (Math.abs(speedx*0.1) > 0.03) {
        speed = Math.abs(speedx*0.1)
      } else {
        speed = item.size * 0.01
      }
      item.size -= speed
      if (item.size <= 0.1){
        this.nodeArr.splice(index,1)
        item = null
        return
      }
      item.draw();
    });
  }
}




// 音频频谱
class GetAudio extends DrawClass {
  context;
  source;
  analyser;
  playing = false
  array
  static instance;
  constructor(audio) {
    super()
    if (!GetAudio.instance) {
      this.context = new AudioContext();
      this.source = this.context.createMediaElementSource(audio); // 创建sourceNode关联audio元素
      this.analyser = this.context.createAnalyser(); // 创建音频分析器
      this.array = new Uint8Array(1024);
      this.init();
      GetAudio.instance = this;
    }
    return GetAudio.instance;
  }
  init() {
    this.source.connect(this.analyser);
    this.analyser.connect(this.context.destination);
    this.gradient = this.ctx.createLinearGradient(0, 0, 0, this.canvas.height/2);
    this.gradient.addColorStop(1, "#0f00f0");
    this.gradient.addColorStop(0.8, "#ff0ff0");
    this.gradient.addColorStop(0.5, "#f00f00");
  }
  draw() {
    const {ctx} = this
    this.analyser.getByteFrequencyData(this.array);  // 获取频域的数据 拷贝到array
    // 音频频谱数组  分成1000份
    this.step = Math.round(this.array.length / 1000);
    ctx.save();
    ctx.fillStyle = this.gradient; //填充
    for (var i = 0; i < 1000; i++) {
      var value = this.array[i * this.step];
      // console.log(this.canHeight);
      ctx.fillRect(i * 15, this.canvas.height/2, 10, -value * 1); //绘制上
      ctx.fillRect(i * 15, this.canvas.height/2, 10, value * .5); //绘制下
    }
    ctx.restore();
    return this.array
  }
}


class DrawClock  extends DrawClass{
  l = 5   // 线条静态长度
  r = 300 // 半径
  r2 = 200 // 第二个圆半径
  rotate = 0 // 整体旋转角度
  rotateAbs = 1 //控制顺逆时针旋转
  scale = 3 // 放大倍数
  scaleAbs = 1 //控制放大缩小

  tempCan // 封面canvas容器
  canWidth = 1000
  canScale = 2.5
  constructor(x,y,postImg,isMobile){
    super()
    this.x = x
    this.y = y
    this.isMobile = isMobile
    this.tempCan = document.createElement('canvas')
    this.tempCan.width = this.canWidth
    this.tempCan.height = this.canWidth

    this.copyPic(postImg || defultImg)
  }
  draw(array){
    const {ctx,canvas,r,r2,scale} = this
    let centerX = canvas.width / 2
    let centerY = canvas.height / 2
    ctx.save()
    ctx.translate(centerX,centerY)
    ctx.scale(scale,scale)
    this.rotate -= this.rotateAbs * .2

    for(let i = 0; i < 360; i++){
      let l = this.l
      let l1 = this.l
      let l2 = this.l
      if (array || array.length > 0) {
        // 转换频谱为长度
        let index = i
        if (index > 180){
          index = 360 - index
        }
        l = this.getLine(array[index+180],10,10)/2
        l1 = this.getLine(array[index],10,2)/2
        // l2 = this.getLine(array[index+90],10,2)/2
      }

      this.drawLine( r - l, r + l, i, -this.rotate)
      this.drawLine( 80 - l1, 80 + l1, i, -this.rotate)
      if(i%1 === 0){
        this.drawLine( r2 , r2 + l2, i, this.rotate, 1)
      }
    }
    ctx.restore()
    // 频谱数值平均数 绘制主频率
    let radio = array.reduce((pre,cur) => {
      return pre + cur
    },0)
    let len = radio/array.length || 0
    // console.log(len);
    if (this.tempCan){
      this.drawArc({x:1520,y:410},len + 1)
      this.drawArc({x:1220,y:420},len + 1)
      this.drawPic(-this.rotate*this.rotateAbs*0.5)
    }

    if (this.scale < 1 || this.scale > 3){
      this.scaleAbs = -this.scaleAbs
    }
    if (this.rotate >360 || this.rotate < 0){
      this.rotateAbs = -this.rotateAbs
    }
    this.scale -= this.scaleAbs * this.scale/1000
    this.canScale -= this.canScale/1000
  }
  // 加载歌曲海报图片
  copyPic(postImg){
    let img = new Image()
    img.src = postImg
    img.onload = () => {
      let tempCtx = this.tempCan.getContext('2d')
      let startX = 0, endX = img.width
      if (img.height < img.width){
        startX = (img.width - img.height)/2
        endX = img.height
      }
      tempCtx.drawImage(img,startX,0,endX,img.height,0,0,this.tempCan.width,this.tempCan.height)
    }
  }
  // 画海报图片(唱片圆形图片)
  drawPic(rotate){
    const {ctx,canScale,canvas,canWidth,canHeight,isMobile} = this
    let centerX = canvas.width / 2
    let centerY = canvas.height / 2
    let scale = (2.5 - canScale) / 2
    if (scale > 0.5){
      scale = 0.5
    }
    ctx.save()
    ctx.translate(centerX,centerY)
    ctx.scale(scale,scale)
    ctx.rotate(getDeg(rotate))
    ctx.beginPath()
    // ctx.strokeStyle = 'transparent'
    ctx.arc(0,0,isMobile ? canWidth/4:canWidth/2,0,2*Math.PI)
    ctx.translate(-canWidth/2,-canWidth/2)
    ctx.fillStyle = ctx.createPattern(this.tempCan, 'no-repeat')
    ctx.fill()
    ctx.stroke()
    ctx.restore()
  }
  /**
   * 
   * @param {number} start 起始点 (以圆心为起点的距离)
   * @param {number} end 结束 (以圆心为起点的距离)
   * @param {number} i (360 圆周度数)
   * @param {number} abs (顺逆时针 1:-1)
   * @param {number} width 线条宽度
   */
  drawLine(start,end,i,rotate,width=1){
    const {ctx} = this
    ctx.save()
    ctx.lineWidth = width
    ctx.rotate(getDeg(rotate))
    ctx.beginPath()
    ctx.strokeStyle = getcolor(i)
    ctx.moveTo(start * getSinCos("cos", i),start * getSinCos("sin", i))
    ctx.lineTo(end * getSinCos("cos", i),end * getSinCos("sin", i))
    ctx.closePath()
    ctx.stroke()
    ctx.restore()
  }
  getLine(l,target,scale){
    if(l === 0){
      return target - target + this.l
    } else if ( l > target ){
      return target + (l-target)/target * scale + this.l
    } else if ( l < target ){
      return target + (l-target)/(l+target) * target + this.l
    }
  }
  drawArc(pos,r=1){
    let {ctx} = this
    ctx.save()
    ctx.globalCompositeOperation = 'lighter';
    // ctx.filter = 'blur(2px)'
    ctx.globalAlpha = r/100
    // 参数依次为：渐变开始圆的圆心x坐标，y坐标，半径；渐变结束圆的圆心x坐标，y坐标，半径
    let gradient = ctx.createRadialGradient(pos.x, pos.y, 1, pos.x, pos.y, r);

    // 设置颜色停靠点
    // 第一个参数是0到1之间的值，表示渐变中的位置；第二个参数是颜色值
    gradient.addColorStop(0, '#fff'); // 圆心处的颜色
    gradient.addColorStop(0.1, '#a1f0a788'); // 圆心处的颜色
    gradient.addColorStop(1, '#0ff0'); // 渐变到边缘的颜色
    ctx.beginPath()
    ctx.arc(pos.x,pos.y,r,0,2*Math.PI)
    ctx.fillStyle = gradient
    ctx.fill()
    ctx.restore()
  }
}


// 渐变色
function getcolor(i,rotate){
  let color1 = '#0f00f0'
  let color2 = '#41f037'
  let newColor1 = getRGB(color1)
  let newColor2 = getRGB(color2)
  let step = 180
  let num1 = (newColor2[0] - newColor1[0])/step 
  let num2 = (newColor2[1] - newColor1[1])/step
  let num3 = (newColor2[2] - newColor1[2])/step

  let rgbArr = []
  let [or,og,ob] = newColor1

  let r = 0,
      g = 0,
      b = 0

  if (i >= 180) {
    r = or + num1 * (360 - i)
    g = og + num2 * (360-i)
    b = ob + num3 * (360-i)
  } else {
    r = or + num1 * i
    g = og + num2 * i
    b = ob + num3 * i
  }
  rgbArr = [r,g,b]
  let str = toHex(rgbArr)
  return str
}
// 转RGB
function getRGB(color){
  let r = parseInt(color.slice(1,3),16)
  let g = parseInt(color.slice(3,5),16)
  let b = parseInt(color.slice(5,7),16)
  return [r,g,b]
}
// 转hex
function toHex(rgbArr){
  let [r,g,b] = rgbArr
  function to16(num){
    return parseInt(num).toString(16).padStart(2,'0')
  }
  return '#' + to16(r) + to16(g) + to16(b)
}


// 解决事件函数中形成的闭包 (oncanplay 和 addeventlistener)
let eventState = {}
eventState.mouseDown = false

/**
 *  此时传入简单数据类型 addEventListener会形成闭包 全局声明一个对象可以解决这个问题
 * @param {*} eventState 全局声明一个对象存储状态和临时数据
 * @param {*} currentWidth 
 * @param {*} setcurrentWidth 
 * @param {*} canvas 
 * @returns 
 */
function useMyEvent(eventState,setcurrentWidth,canvas){

  // 不能使用currentWidth  传入时是个简单数据类型 进入到addEventListener 后面再更改时不会更新事件函数内部的变量
  let l = window.innerWidth


  const curnttMosDwn = function(e) {
    eventState.mouseDown = true
    eventState.currentWidth = parseInt(e.clientX*10000/l)
    setcurrentWidth(parseInt(e.clientX*10000/l))
    document.addEventListener('mousemove',mousemv)
    document.addEventListener('mouseup',mouseup)
  }

  const mousemv = (e) => {
    if (eventState.mouseDown) {
      eventState.currentWidth = parseInt(e.clientX*10000/l)
      setcurrentWidth(parseInt(e.clientX*10000/l))
    }
  }
  const mouseup = () => {
    if (eventState.mouseDown){
      let audio = document.getElementById('audio')
      audio.currentTime = eventState.currentWidth/10000 * audio.duration
      document.removeEventListener('mousemove',mousemv)
      document.removeEventListener('mouseup',mouseup)
    }
    eventState.mouseDown = false
  }



  const paly = () => {
    let audio = document.getElementById('audio')
    audio.play()
  }
  const pause = () => {
    console.log('hha');
    let audio = document.getElementById('audio')
    audio.pause()
  }

  return {
    curnttMosDwn,
    paly,
    pause
  }
}






function Canvas(props) {
  let {play,pause,playnext,playpre,volChange,isMobile} = props

  let reqId = null
  const audioCtx = useRef(null)
  const can = useRef(null);
  const btn1 = useRef(null);
  const btn2 = useRef(null);
  const btn3 = useRef(null);
  const volIpt = useRef(null);
  const total = useRef(null);
  const clockInstance = useRef(null)
  let playing = false
  let duration = 0

  const [currentWidth,setcurrentWidth] = useState(0)
  const [currentTime,setcurrentTime] = useState('00:00 / 00:00')

  useEffect( () => {
    // 初始化 获取audio  canvas对象
    let audio = document.getElementById('audio')
    const canvas = can.current;
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    let ctx = canvas.getContext("2d");
    
    DrawClass.ctx = ctx
    DrawClass.canvas = canvas

    
    let MouseMove = Factory(DrawHeart,{limit:1000})  //  最大数量限制: 1000
    let PointMove = Factory(DrawHeart,{
      limit:100,
      speedx:() => (Math.random()*0.5 - 0.5) * 0.005,
      speedy:() => (Math.random() - 0.5) * 0.002,
      sizespeed: 0.005
    }) //  最大数量限制: 100

    function mouseEvent(e) {
      e.clientX, e.clientY
      new MouseMove(e.clientX, e.clientY, rdmNum(25)+15)
    }
    
    document.onmousemove = deb(mouseEvent,10)
    audio.oncanplay = () => {
      duration = audio.duration
    }
    
    audio.ontimeupdate = () => {
      !playing && (playing = true)
      if (!eventState.mouseDown){
        setcurrentWidth(parseInt(audio.currentTime*10000/duration)) // 注意 除法容易得到infinite
      }
      setcurrentTime(getAudioTime(audio.currentTime) + " / " + getAudioTime(duration))
    }
    
    let state = {}
    
    // 随机心飘落
    state.id = setInterval(() => {
      new FlyStar(rdmNum(1500), rdmNum(1500), rdmNum(1));
      new FlyHeart(rdmNum(1500), rdmNum(1500), rdmNum(25)+25);
      let x = parseInt(audio.currentTime*10000/duration)*canvas.width/10000-20
      for(let i = 0; i< 1; i++){
        new PointMove(x, canvas.height/2, rdmNum(5)+7)
      }
    }, 200);
    // 获取集合实例
    let stars = new FlyStar();
    let hearts = new FlyHeart();
    let mouse = new MouseMove()
    let points = new PointMove()
    let clock = new DrawClock(100,100,props.postImg,isMobile)
    // 保存实例  切换歌曲时更换post图片
    clockInstance.current = clock

    audio.onplaying = () => {
      audioCtx.current = new GetAudio(audio)
    }
    
    
    // 动画
    function run() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      if (audioCtx.current && playing){
        audioCtx.current.draw()
      }
      clock.draw(audioCtx.current?.array || [])

      stars.fly()
      hearts.fly();
      mouse.move()
      points.move()

      state.reqId = requestAnimationFrame(run);
    }
    
    run();


    window.onresize = () => {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
    };

    return () => {
      window.onresize = null
      clearInterval(state.id)
      cancelAnimationFrame(state.reqId)
      GetAudio.instance = null
      FlyHeart.instance = null
      MouseMove.instance = null
      document.onmousemove = null
      audio.oncanplay = null
      audio.ontimeupdate = null
    };
  }, []);

  useEffect(() => {
  },[props.postImg])

  let {curnttMosDwn} = useMyEvent(eventState,setcurrentWidth,can.current)




  return  (
    <div className="canvaspage">
      <canvas ref={can} id="canvas"></canvas>
      <div onMouseDown={curnttMosDwn} className="progress">
        <div ref={total} id="total"  data-value={currentTime} className="total">
          <div style={{width: currentWidth/100 + '%'}} className="current">
            <div  className="point"></div>
          </div>
        </div>
      </div>
      <div className="container">
        <div className="box">
          <div ref={btn1} onClick={playpre} id="btn4" className="btn">
            <a>上一曲</a>
          </div>
        </div>
        <div className="box">
          <div ref={btn1} onClick={play} id="btn1" className="btn">
            <a>播 放</a>
          </div>
        </div>
        <div className="box">
          <div ref={btn2} onClick={pause} id="btn2" className="btn">
            <a>暂 停</a>
          </div>
        </div>
        <div className="box">
          <div ref={btn3} onClick={playnext} id="btn3" className="btn">
            <a>下一曲</a>
          </div>
        </div>
      </div>
      <div className="volume">
        <input ref={volIpt} onChange={volChange} type="range" min="1" max="100" defaultValue={100} name="vol" id="" />
      </div>
      {/* <footer>
        <a className="beian" src="https://beian.miit.gov.cn/" target="_blank">
          豫ICP备2023008742号
        </a>
      </footer> */}
    </div>
  );
}

export default Canvas;
